package algorithm;

import entity.DeviceType;
import entity.FourTuple;
import entity.TwoTuple;
import problem.MLSIAP;
import problem.Problem;
import solution.WaveForMLSIAP;
import util.GaussRandom;

public class WWOAlgForMLSIAP
		extends WWOAlgorithm<Integer, Double, WaveForMLSIAP, FourTuple<Double, Double, Integer, Double>> {
	/* 针对MLSIAP的WWO算法特有的属性 */
	double lambdaMax;
	double lambdaMin;

	MLSIAP Mproblem;
	int instanceId;
	int N1, N2, N;
	int M1, M2, M;
	int groupUpper, groupLower;
	int S;
	int L;

	public WWOAlgForMLSIAP(Problem<Integer, FourTuple<Double, Double, Integer, Double>, Double> problem, boolean isMax,
			String algName, int popSize, int NFE, int hmax, double alpha, double betaMax, double betaMin, int kmax,
			double lambdaMax, double lambdaMin) {
		super(problem, isMax, algName, popSize, NFE, hmax, alpha, betaMax, betaMin, kmax, lambdaMax);
		Mproblem = (MLSIAP) problem;
		getParameters();
		this.lambdaMax = lambdaMax;
		this.lambdaMin = lambdaMin;
	}

	private void getParameters() {
		instanceId = Mproblem.getInstanceId();
		N1 = Mproblem.getN1();
		N2 = Mproblem.getN2();
		N = Mproblem.getN();
		M1 = Mproblem.getM1();
		M2 = Mproblem.getM2();
		M = Mproblem.getM();
		groupUpper = Mproblem.getUpper();
		groupLower = Mproblem.getLower();
		S = Mproblem.getS();
		L = groupUpper - groupLower;
	}

	@Override
	public void initialize() {
		for (int i = 0; i < popSize; i++) {
			int j = 0;
			Integer[] content = new Integer[N + M];
			// 产生乘客分组的解
			for (; j < N; j++) {
				content[j] = random.nextInt(groupUpper - groupLower + 1) + groupLower;
			}
			// 产生设备人员分配的解
			assignInspectors(content);

			WaveForMLSIAP wave = new WaveForMLSIAP(content, groupUpper, groupLower, i, M + N, hmax, lambda);
			population.add(wave);
		}

	}

	/**
	 * 根据每个设备th=Th情况下，检查人员检查乘客的总时间，降序排序，从前往后分配检查人员
	 * 
	 * @param content
	 */
	private void assignInspectors(Integer[] content) {
		DeviceType[] devices = Mproblem.calTotolTimeOfEveryDevice(content);
		// 防止空指针异常
		for (int i = N; i < N + M; i++) {
			content[i] = 0;
		}
		int temp = S;
		for (int i = 0; i < devices.length; i++) {
			// 没有可以额外分配的检查人员
			if (temp == 0) {
				break;
			} else if (temp >= devices[i].getNDh()) {
				content[N + devices[i].getH() - 1] = devices[i].getNDh();
				// 把分配的检查人员减掉
				temp -= devices[i].getNDh();
			} else {
				content[N + devices[i].getH() - 1] = temp;
				temp = 0;
			}
		}
	}

	/**
	 * 对一个解进行评估，每次评估后需要对current_NFE+1
	 * 
	 * @param wave
	 */
	public void evaluateOneWave(WaveForMLSIAP wave) {
		// 计算适应度和约束值
		FourTuple<Double, Double, Integer, Double> result = Mproblem.calculate(wave);
		wave.setValue(result.getFirst());
		wave.setValueOfPFC(result.getSecond());
		wave.setTotalS(result.getThrid());
		wave.setMaxTime(result.getFourth());
		// 增加函数评估次数
		current_NFE++;
		// 统计
		if (current_NFE % 1000 == 0) {
			TwoTuple<Integer, WaveForMLSIAP> record = new TwoTuple<Integer, WaveForMLSIAP>(current_NFE, best);
			records.add(record);
		}
	}

	@Override
	protected void evaluate() {
		WaveForMLSIAP[] solutions = new WaveForMLSIAP[popSize];
		// 对种群中的每个解执行评估
		for (int i = 0; i < popSize; i++) {
			WaveForMLSIAP solution = population.get(i);
			// 计算适应度和约束值
			evaluateOneWave(solution);
			// 使用插入排序，将种群由小到大排序
			solutions[i] = solution;
			for (int j = i; j >= 1; j--) {
				if (solutions[j].getValue() < solutions[j - 1].getValue()) {
					WaveForMLSIAP temp = solutions[j];
					solutions[j] = solutions[j - 1];
					solutions[j - 1] = temp;
				}
			}
		}
		// 得到最优解,最差解
		best = solutions[0];
		worst = solutions[solutions.length - 1];

	}

	@Override
	public void evolve() {
		for (int i = 0; i < population.size(); i++) {
			// 注意这里是取引用，wave中保存的是引用地址
			WaveForMLSIAP wave = population.get(i);
			// 传播操作
			WaveForMLSIAP newWave = propagate(wave);
			if (newWave.getValue() < wave.getValue()) {
				if (newWave.getValue() < best.getValue()) {
					// 碎浪操作
					best = breaking(newWave);
					wave = best;
				} else {
					wave = newWave;
				}
				// 更新高度
				wave.setHeight(hmax);
			} else {
				// 降低高度
				// 因为newWave并没有比原来的wave更优，所以不替换
				wave.setHeight(wave.getHeight() - 1);
				// 折射操作
				if (wave.getHeight() == 0) {
					newWave = refract(wave);
					// 更新高度
					newWave.setHeight(hmax);
					// 替换原来的wave
					wave = newWave;
				}
			}
			// 更新种群中的波
			population.set(i, wave);
		}
		// 更新最差解，用于波长计算
		worst = population.get(0);
		for (WaveForMLSIAP wave : population) {
			if (wave.getValue() > worst.getValue()) {
				worst = wave;
			}
			// refract以后可能产生比best更优秀的解
			if (wave.getValue() < best.getValue()) {
				best = wave;
			}
		}

		// 更新每个wave的波长
		for (WaveForMLSIAP wave : population) {
			double newLength = wave.getLength();
			newLength = newLength * Math.pow(alpha,
					-(worst.getValue() - wave.getValue() + epsilon) / (worst.getValue() - best.getValue() + epsilon));
			// 为波长设置下限
			if (newLength < lambdaMin) {
				newLength = lambdaMin;
			}
			wave.setLength(newLength);
		}

	}

	@SuppressWarnings("finally")
	@Override
	public WaveForMLSIAP propagate(WaveForMLSIAP wave) {
		WaveForMLSIAP newWave = null;
		try {
			// 复制原来的解用来传播
			newWave = (WaveForMLSIAP) wave.clone();
			Integer[] content = newWave.getContent();
			// 对前N个乘客做出分组
			for (int d = 0; d < N; d++) {
				content[d] = Math
						.round((float) (content[d] + (random.nextDouble() * 2 + (-1)) * newWave.getLength() * L));
				// 检查边界
				checkBound(groupUpper, groupLower, content, d);
			}
			// 安排每个设备的检查人员
			assignInspectors(content);
			// 重新评估
			evaluateOneWave(newWave);

		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		} finally {
			return newWave;
		}
	}

	@Override
	public WaveForMLSIAP refract(WaveForMLSIAP wave) {
		WaveForMLSIAP newWave = null;
		try {
			newWave = (WaveForMLSIAP) wave.clone();
			Integer[] content = newWave.getContent();
			Integer[] bestContent = best.getContent();
			for (int d = 0; d < N; d++) {
				GaussRandom r = new GaussRandom((bestContent[d] + content[d]) / 2,
						Math.abs(bestContent[d] - content[d]) / 2);
				content[d] = Math.round((float) r.nextGuassianDouble());
				// 边界检查
				checkBound(groupUpper, groupLower, content, d);
			}
			// 分配检查人员
			assignInspectors(content);
			// 重新评估
			evaluateOneWave(newWave);

			// 波长计算需要修改
			double newLength = wave.getLength() * newWave.getValue() / wave.getValue();
			if (newLength < lambdaMin) {
				newLength = lambdaMin;
			} else if (newLength > lambdaMax) {
				newLength = lambdaMax;
			}
			newWave.setLength(newLength);

		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return newWave;
	}

	@Override
	public WaveForMLSIAP breaking(WaveForMLSIAP wave) {
		// 从D维中选出k维
		int[] kArr = selectKDimension();
		WaveForMLSIAP best = wave;
		// 生成候选解，选出最优的解
		for (int i = 0; i < kArr.length; i++) {
			try {
				GaussRandom gaussRandom = new GaussRandom();
				WaveForMLSIAP candidate = (WaveForMLSIAP) wave.clone();
				Integer[] content = candidate.getContent();
				content[kArr[i]] = Math.round((float) (content[kArr[i]] + gaussRandom.nextGuassianDouble() * beta * L));
				// 检查边界
				checkBound(groupUpper, groupLower, content, kArr[i]);
				// 重新分配检查人员
				assignInspectors(content);
				// 评估
				evaluateOneWave(candidate);
				if (candidate.getValue() < best.getValue()) {
					best = candidate;
				}
			} catch (CloneNotSupportedException e) {
				e.printStackTrace();
			}
		}

		return best;
	}

	private int[] selectKDimension() {
		final int k = random.nextInt(kmax) + 1;
		int[] kArr = new int[k];
		for (int i = 0; i < k; i++) {
			// 从N维里面选出k维
			int temp = random.nextInt(N);
			// 判断产生的值是否唯一
			boolean isUnique = false;
			int j = i - 1;
			while (j >= 0 && !isUnique) {
				for (; j >= 0; j--) {
					if (temp == kArr[j]) {
						j = i - 1;
						// 重新产生随机数
						temp = random.nextInt(N);
						break;
					}
				}
				if (j == -1) {
					isUnique = true;
				}
			}
			kArr[i] = temp;
		}
		return kArr;
	}

	/**
	 * 
	 * @param upper
	 * @param lower
	 * @param content
	 * @param d
	 *            维度
	 * @return
	 */
	protected boolean checkBound(int upper, int lower, Integer[] content, int d) {
		if (content[d] >= lower && content[d] <= upper) {
			return true;
		}
		content[d] = random.nextInt((upper - lower + 1)) + lower;
		return false;
	}

}
